package com.changge.module.scheduler.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.changge.common.core.utils.IdUtil;
import com.changge.common.core.utils.StringUtil;
import com.changge.common.core.utils.bean.BeanCopyUtil;
import com.changge.common.models.domain.system.scheduler.SchedulerJob;
import com.changge.common.models.dto.system.scheduler.SchedulerJobDTO;
import com.changge.common.models.dto.system.scheduler.SchedulerJobStatusDTO;
import com.changge.common.models.query.system.scheduler.SchedulerJobQuery;
import com.changge.module.scheduler.constant.SchedulerJobConst;
import com.changge.module.scheduler.enums.JobExecuteStrategyEnum;
import com.changge.module.scheduler.enums.SchedulerJobStatusEnum;
import com.changge.module.scheduler.exception.SchedulerErrorCode;
import com.changge.module.scheduler.mapper.SchedulerJobMapper;
import com.changge.module.scheduler.utils.CronUtil;
import com.changge.module.scheduler.utils.SchedulerJobBuildUtil;
import com.changge.module.scheduler.utils.SchedulerJobValidUtil;
import com.changge.module.security.utils.SecurityUtil;
import com.changge.scheduler.facade.service.ISchedulerJobService;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 定时任务业务逻辑接口实现类
 * </p>
 *
 * @author zhangrongkang
 * @since 2024/2/23
 */
@Slf4j
@Service
public class SchedulerJobServiceImpl implements ISchedulerJobService {

    @Autowired
    private IdUtil idUtil;

    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private SchedulerJobMapper schedulerJobMapper;

    /**
     * 初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理（注：不能手动修改数据库ID和任务组名，否则会导致脏数据）
     */
    @PostConstruct
    public void init() throws SchedulerException {
        scheduler.clear();
        List<SchedulerJob> jobList = listAllSchedulerJob();
        for (SchedulerJob job : jobList) {
            SchedulerJobBuildUtil.buildScheduleJob(scheduler, BeanCopyUtil.copy(job, SchedulerJobDTO.class));
        }
        log.info("load scheduler job success");
    }

    /**
     * 获取所有定时任务
     *
     * @param schedulerJobQuery 定时任务查询对象
     * @return 定时任务分页数据
     */
    @Override
    public Page<SchedulerJob> listSchedulerJob(SchedulerJobQuery schedulerJobQuery) {
        // 开启分页
        Page<SchedulerJob> page = new Page<>(schedulerJobQuery.getCurrentPage(), schedulerJobQuery.getPageSize());
        QueryWrapper<SchedulerJob> queryWrapper = getQueryWrapper(schedulerJobQuery);
        // 查询分页
        return schedulerJobMapper.selectPage(page, queryWrapper);
    }

    /**
     * 获取查询条件
     *
     * @param schedulerJobQuery 定时任务查询对象
     * @return 定时任务查询条件
     */
    private QueryWrapper<SchedulerJob> getQueryWrapper(SchedulerJobQuery schedulerJobQuery) {
        // 定义查询条件
        QueryWrapper<SchedulerJob> queryWrapper = new QueryWrapper<>();
        // 任务编号
        if (Objects.nonNull(schedulerJobQuery.getId())) {
            queryWrapper.eq("id", schedulerJobQuery.getId());
        }
        // 调用目标字符串
        if (StringUtil.isNoneBlank(schedulerJobQuery.getInvokeTarget())) {
            queryWrapper.like("invoke_target", schedulerJobQuery.getInvokeTarget());
        }
        // 任务名称
        if (StringUtil.isNoneBlank(schedulerJobQuery.getJobName())) {
            queryWrapper.eq("job_name", schedulerJobQuery.getJobName());
        }
        // 任务分组
        if (StringUtil.isNoneBlank(schedulerJobQuery.getJobGroup())) {
            queryWrapper.eq("job_group", schedulerJobQuery.getJobGroup());
        }
        // 任务状态
        if (Objects.nonNull(schedulerJobQuery.getStatus())) {
            queryWrapper.eq("status", schedulerJobQuery.getStatus());
        }
        // 获取时间范围
        List<String> dataRange = schedulerJobQuery.getDataRange();
        // 如果时间范围不为空
        if (!CollectionUtils.isEmpty(dataRange)) {
            // 拼接时间范围查询条件
            queryWrapper.between("create_time", dataRange.get(0), dataRange.get(1));
        }
        return queryWrapper;
    }

    /**
     * 添加或更新定时任务
     *
     * @param schedulerJobDTO 定时任务数据接收对象
     * @return 是否添加/更新成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveSchedulerJob(SchedulerJobDTO schedulerJobDTO) {
        log.info("schedulerJobDTO: {}", schedulerJobDTO);
        SchedulerJobValidUtil.validJobParam(schedulerJobDTO);
        // 根据是否包含ID来判断添加-更新操作
        if (null != schedulerJobDTO.getId()) {
            // 更新定时任务
            return updateSchedulerJob(schedulerJobDTO);
        }
        // 添加定时任务
        return insertSchedulerJob(schedulerJobDTO);
    }

    /**
     * 获取指定定时任务信息
     *
     * @param schedulerJobId 定时任务ID
     * @return 定时任务数据返回对象
     */
    @Override
    public SchedulerJob getSchedulerJobById(Long schedulerJobId) {
        return schedulerJobMapper.selectById(schedulerJobId);
    }
    
    /**
     * 批量删除定时任务数据
     *
     * @param ids 定时任务ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteBatch(List<Long> ids) {
        for (Long id : ids) {
            // 删除定时任务
            SchedulerJob schedulerJob = schedulerJobMapper.selectById(id);
            deleteSchedulerJob(id, schedulerJob.getJobGroup());
        }
    }

    /**
     * 修改定时任务状态
     *
     * @param schedulerJobStatusDTO 定时任务状态数据传输对象
     * @return 是否修改成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean changeJobStatus(SchedulerJobStatusDTO schedulerJobStatusDTO) {
        // 定时任务ID
        Long jobId = schedulerJobStatusDTO.getId();
        // 任务分组
        String jobGroup = schedulerJobStatusDTO.getJobGroup();
        // 修改后的状态
        boolean status = schedulerJobStatusDTO.getStatus();
        // 根据修改后的状态来执行定时任务操作
        return status ? resumeSchedulerJob(jobId, jobGroup) : pauseSchedulerJob(jobId, jobGroup);
    }

    /**
     * 恢复定时任务
     *
     * @param jobId 任务ID
     * @param jobGroup 任务分组
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean resumeSchedulerJob(Long jobId, String jobGroup) {
        // 获取JobKey
        JobKey jobKey = SchedulerJobBuildUtil.buildJobKey(jobId, jobGroup);
        try {
            // 执行定时任务恢复操作
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_JOB_RESUME_ERROR.exception();
        }
        // 获取状态
        Boolean status = SchedulerJobStatusEnum.NORMAL.getValue();
        // 更新数据库定时任务
        return schedulerJobMapper.updateJobStatus(jobId, status) == 1;
    }

    /**
     * 暂停定时任务
     *
     * @param jobId    任务ID
     * @param jobGroup 任务分组
     * @return 是否暂停成功
     */
    @Override
    public Boolean pauseSchedulerJob(Long jobId, String jobGroup) {
        // 获取JobKey
        JobKey jobKey = SchedulerJobBuildUtil.buildJobKey(jobId, jobGroup);
        try {
            // 执行定时任务暂停操作
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_JOB_PAUSE_ERROR.exception();
        }
        // 获取状态
        Boolean status = SchedulerJobStatusEnum.PAUSE.getValue();
        // 更新数据库定时任务状态
        return schedulerJobMapper.updateJobStatus(jobId, status) == 1;
    }

    /**
     * 删除定时任务
     *
     * @param jobId    任务ID
     * @param jobGroup 任务分组
     * @return 是否删除成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteSchedulerJob(Long jobId, String jobGroup) {
        // 获取JobKey
        JobKey jobKey = SchedulerJobBuildUtil.buildJobKey(jobId, jobGroup);
        try {
            scheduler.deleteJob(jobKey);
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_JOB_DELETE_ERROR.exception();
        }
        // 返回数据删除执行结果
        return schedulerJobMapper.deleteById(jobId) == 1;
    }

    /**
     * 执行一次定时任务
     *
     * @param schedulerJobId 定时任务ID
     * @return 是否执行成功
     */
    @Override
    public Boolean run(Long schedulerJobId) {
        boolean result = false;
        SchedulerJobDTO schedulerJobDTO = BeanCopyUtil.copy(schedulerJobMapper.selectById(schedulerJobId), SchedulerJobDTO.class);

        // 构建参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(SchedulerJobConst.TASK_PROPERTIES, schedulerJobDTO);

        // 构建JobKey
        JobKey jobKey = SchedulerJobBuildUtil.buildJobKey(schedulerJobId, schedulerJobDTO.getJobGroup());
        try {
            if (scheduler.checkExists(jobKey)) {
                result = true;
                scheduler.triggerJob(jobKey, dataMap);
            } else {
                if (CronUtil.validCronExpire(schedulerJobDTO.getCron())) {
                    throw SchedulerErrorCode.CRON_EXPRESSION_EXPIRED.exception();
                }
            }
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_JOB_EXECUTE_ERROR.exception();
        }
        return result;
    }

    /**
     * 构建不可并发定时任务对象
     *
     * @param time 定时任务执行日期
     * @return 定时任务数据传输对象
     */
    @Override
    public SchedulerJobDTO buildNonConcurrentSchedulerJobDTO(String time) {
        return SchedulerJobDTO.builder()
                // 不可并发
                .concurrent(false)
                // Cron表达式
                .cron(CronUtil.convertTimeToCron(time))
                // 执行一次
                .executeStrategy(JobExecuteStrategyEnum.EXECUTE_ONCE.getValue())
                // 创建人
                .createBy(securityUtil.getLoginUsername())
                // 任务状态正常
                .status(SchedulerJobStatusEnum.NORMAL.getValue()).build();
    }

    /**
     * 查询所有定时任务
     *
     * @return 所有定时任务集合
     */
    @Override
    public List<SchedulerJob> listAllSchedulerJob() {
        return schedulerJobMapper.selectList(null);
    }

    /**
     * 更新当前定时任务
     *
     * @param schedulerJobDTO 定时任务数据接收对象
     * @return 是否更新成功
     */
    private Boolean updateSchedulerJob(SchedulerJobDTO schedulerJobDTO) {
        // 设定更新者
        schedulerJobDTO.setUpdateBy(securityUtil.getLoginUsername());
        // 对定时任务进行更新操作并返回响应结果
        boolean result = schedulerJobMapper.updateById(BeanCopyUtil.copy(schedulerJobDTO, SchedulerJob.class)) == 1;
        // 获取JobKey
        JobKey jobKey = SchedulerJobBuildUtil.buildJobKey(schedulerJobDTO.getId(), schedulerJobDTO.getJobGroup());
        // 先删除原本的定时任务防止创建时存在数据问题
        try {
            if (scheduler.checkExists(jobKey)) {
                // 定时任务存在则删除
                scheduler.deleteJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw SchedulerErrorCode.SCHEDULER_JOB_DELETE_ERROR.exception();
        }
        // 构建定时任务
        SchedulerJobBuildUtil.buildScheduleJob(scheduler, schedulerJobDTO);
        return result;
    }
    
    /**
     * 添加新的定时任务
     *
     * @param schedulerJobDTO 定时任务数据接收对象
     * @return 是否添加成功
     */
    private Boolean insertSchedulerJob(SchedulerJobDTO schedulerJobDTO) {
        // 定时任务ID
        schedulerJobDTO.setId(idUtil.nextId());
        // 任务创建者
        schedulerJobDTO.setCreateBy(securityUtil.getLoginUsername());
        // 添加定时任务数据并返回添加结果
        boolean result = schedulerJobMapper.insert(BeanCopyUtil.copy(schedulerJobDTO, SchedulerJob.class)) == 1;
        if (result) {
            // 创建定时任务
            SchedulerJobBuildUtil.buildScheduleJob(scheduler, schedulerJobDTO);
        }
        return result;
    }

}
